package com.qin.aspect;

import com.google.gson.Gson;
import com.qin.properties.LogProperties;
import com.qin.protobuf.LogClientSendReqProto;
import com.qin.service.LogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Parameter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Optional;

/**
 * 配置 AOP 切面
 *
 * TIPS：ConditionalOnProperty 注解控制Configuration 是否生效。简单来说也就是我们可以通过在yml配置文件中控制Configuration
 *       注解的配置类是否生效。例如，本例中只有 qin.log.open = true 时这个配置类才会生效
 *
 * @author qinxh
 * @date 2020/08/12
 */
@Aspect
@Component
@ConditionalOnProperty(
        prefix = "qin.log",
        name = "open",
        havingValue = "true"
)
@Order(Integer.MAX_VALUE)
public class CallLogAspect {

    @Resource
    private LogService logService;

    /**
     * 参数配置类
     */
    @Autowired
    private LogProperties logProperties;

    /**
     * 被收集服务的ip
     */
    private String hostAddress;

    private Gson gson = new Gson();

    public CallLogAspect() throws UnknownHostException {

        this.hostAddress = InetAddress.getLocalHost().getHostAddress();
    }

    /**
     * 以自定义 @CallLog 注解为切点
     */
    @Pointcut("@annotation(com.qin.annotation.CallLog)")
    public void callLog() { }

    /**
     * 环绕
     * @param joinPoint   连接点对象
     * @return Object
     * @throws Throwable
     */
    @Around("callLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        // 记录一下调用接口的开始时间
        long startTime = System.currentTimeMillis();
        // 参数列表
        Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
        Object[] args = joinPoint.getArgs();
        StringBuilder params = new StringBuilder("[");
        for (int i = 0; i < parameters.length; i++) {
            params.append(parameters[i].getName()).append(":").append(gson.toJson(args[i])).append("; ");
        }
        if (parameters.length > 0) {
            int length = params.length();
            params.delete(length - 2, length);
        }
        params.append("]");
        // 打印出参
        Object result;
        try {
            // 执行方法
            result = joinPoint.proceed();
        } catch (Exception e) {
            // 记录异常日志
            result = e.getMessage();
        }

        // 使用protobuf传输数据
        LogClientSendReqProto.LogClientSendReq.Builder builder = LogClientSendReqProto.LogClientSendReq.newBuilder();
        // 目标类的名称
        builder.setClassName(joinPoint.getTarget().getClass().getName());
        // 方法名称
        builder.setMethodName(joinPoint.getSignature().getName());
        // 执行耗时（如果太短，指定为1）
        long timeConsuming = System.currentTimeMillis() - startTime;
        builder.setTimeConsuming(timeConsuming == 0L ? 1 : Integer.valueOf(String.valueOf(timeConsuming)));
        // 参数列表
        builder.setParams(params.toString());
        // 返回结果
        builder.setResult(Optional.ofNullable(result).orElse("无").toString());
        // 日志类型
        builder.setLogType(LogClientSendReqProto.LogClientSendReq.LogType.ERROR);
        // 被收集服务的ip
        builder.setCollectServiceIp(hostAddress);
        // 被收集服务的服务名
        builder.setCollectServiceName(logProperties.getCollectServiceName());

        // 得到channel，并发送消息
        logService.getChannel().writeAndFlush(builder.build());

        return result;
    }
}
